package com.hyt.it.ogt.kq.service.gov.schedule.impl;

import java.util.ArrayList;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.UUID;

import com.hyt.it.ogt.kq.service.gov.schedule.ExamRoomSchedule;
import com.hyt.it.ogt.kq.service.gov.schedule.model.*;
import com.hyt.it.ogt.kq.service.gov.schedule.statics.ScheduleStaticResult;
import com.hyt.it.ogt.kq.service.gov.schedule.statics.ScheduleStatics;
import com.hyt.it.ogt.kq.service.gov.schedule.strategy.ScheduleHelper;
import com.hyt.it.ogt.kq.service.gov.schedule.strategy.ScheduleStrategyFactory;
import com.hyt.it.ogt.kq.service.gov.schedule.utils.ExamInfoUtils;
import com.hyt.it.ogt.kq.service.gov.schedule.utils.SupportUtils;
import com.hyt.it.ogt.kq.service.gov.schedule.utils.ScheduleUtils;
import com.hyt.it.ogt.kq.service.gov.schedule.utils.ExamInfoUtils.PairExamInfoAndExamRoom;

import lombok.Data;

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ExamRoomScheduleImpl implements ExamRoomSchedule {
    private ScheduleGroupHandler scheduleGroupHandler;
    private static final Logger logger = LoggerFactory.getLogger(ExamRoomSchedule.class);

    public ExamRoomScheduleImpl() {
        this.scheduleGroupHandler = new ScheduleGroupHandler();
    }

    /**
     * 实体考场编排入口
     */
    @Override
    public ImmutablePair<List<ScheduleResult>, List<ExamRoom>> schedule(ScheduleConstrain scheduleConstrain,
            List<ExamInfo> totalExamInfos,
            List<ExamRoom> totalExamRooms) {
        // 将编排任务根据应答方式、考场科目进行拆分成子任务
        List<PairExamInfoAndExamRoom> splitsScheduleTasks = ExamInfoUtils.splitExamInfos(totalExamInfos,
                totalExamRooms);
        logger.info("total task split to {} sub tasks", splitsScheduleTasks.size());
        Map<String, ScheduleResult> scheduleResultMap = new HashMap<>();
        ScheduleHelper scheduleHelper = ScheduleHelper.getInstance();

        // 对拆分的子任务开始进行编排
        for (PairExamInfoAndExamRoom pairExamInfoAndExamRoom : splitsScheduleTasks) {
            try {

                List<ExamInfo> examInfos = pairExamInfoAndExamRoom.getPairExamInfos();
                List<ExamRoom> examRooms = pairExamInfoAndExamRoom.getPairExamRooms();

                // 初始化考场，主要是根据考场容量，初始化考场的座位
                this.initExamRoom(examRooms);

                // 将考场信息增加到编排缓存
                examRooms.forEach(item -> {
                    scheduleHelper.getExamRoomMap().put(item.getRoomId(), item);
                });

                // 对考生和考场进行分组处理，同时生成考场考生对应的虚拟编排考生ID
                // 主要目的：1）考生和考场建立对应关系。2）将多个考生组合在一起转换成编排用的虚拟考生
                this.scheduleGroupHandler.scheduleGroup(scheduleConstrain, examRooms, examInfos);

                // 编排信息 <批次，<分组名，考生和考场组合>>
                Map<String, Map<String, PackageExamRoomCandidate>> batchPackageGroupMap = scheduleHelper
                        .getBatchPackageGroupMap();

                // 编排批次号
                List<String> batchNoList = batchPackageGroupMap.keySet().stream().collect(Collectors.toList());
                Collections.sort(batchNoList);

                // 将所有批次的分组信息混合在一起。<组合名, <考生和考场组合>>

                Map<String, List<PackageExamRoomCandidate>> batchGroupPackageMap = SupportUtils
                        .extractPackage(batchPackageGroupMap);

                // 按组合开始进行编排
                for (Map.Entry<String, List<PackageExamRoomCandidate>> entry : batchGroupPackageMap.entrySet()) {
                    String groupName = entry.getKey();
                    Set<Candidate> candidateSet = new HashSet<>();
                    Set<ExamRoom> examRoomSet = new HashSet<>();
                    entry.getValue().forEach(item -> {
                        item.getCandidates().stream().forEach(citem -> {
                            candidateSet.addAll(citem);
                        });
                        examRoomSet.addAll(item.getExamRooms());
                    });

                    // 对组合内的考生、考场进行编排
                    List<ScheduleResult> groupScheduleResult = this.schedule(scheduleConstrain, examInfos, groupName,
                            batchNoList,
                            examRoomSet, candidateSet);

                    // 将编排后的结果，按 "批次号：编排结果 " 这种格式保存下来
                    for (ScheduleResult scheduleResult : groupScheduleResult) {
                        if (!scheduleResultMap.containsKey(scheduleResult.getBatchNo())) {
                            scheduleResult.setPackageGroupMap(batchPackageGroupMap.get(scheduleResult.getBatchNo()));
                            scheduleResultMap.put(scheduleResult.getBatchNo(), scheduleResult);
                        } else {
                            scheduleResultMap.get(scheduleResult.getBatchNo()).getExamRooms()
                                    .addAll(scheduleResult.getExamRooms());
                        }
                    }
                }
            } finally {
                this.clear();
            }

        }
        List<ScheduleResult> scheduleResults = scheduleResultMap.values().stream().collect(Collectors.toList());
        // 对编排结果进行统计，主要用于输出日志信息
        ScheduleStaticResult staticResult = ScheduleStatics.totalStaticSchedule(scheduleConstrain,
                scheduleResults,
                totalExamRooms);
        logger.info("schedule over static result {}", staticResult);
        return ImmutablePair.of(scheduleResults, totalExamRooms);
    }

    /***
     * 根据批次进行考场分配
     */
    @Override
    public List<ScheduleResult> schedule(ScheduleConstrain scheduleConstrain, List<ExamInfo> examInfos,
            String groupName,
            List<String> batchNoList,
            Set<ExamRoom> examRoomSet, Set<Candidate> candidates) {
        logger.info("start schedule exam {}, group name {}, exam room number {} candidate number {}", scheduleConstrain,
                groupName, examRoomSet.size(), candidates.size());

        // 取得该组编排所有考生对应的虚拟考生ID
        List<String> virCandidateIds = ScheduleUtils.batchGetCandidateIds(candidates,
                ScheduleHelper.getInstance().getVirId2CandidateIdMap());

        List<ExamRoom> examRooms = new ArrayList<>(examRoomSet);

        // 选择编排策略，进行编排
        if("1".equals(scheduleConstrain.getArrangeConfig())){
            scheduleConstrain.setStrategy(ScheduleStrategyEnum.BALANCE);
        }
        List<ScheduleResult> scheduleResults = ScheduleStrategyFactory
                .getScheduleStrategy(scheduleConstrain.getStrategy())
                .apply(scheduleConstrain, examInfos, groupName, batchNoList, examRooms, virCandidateIds);

        // 按要求，将原考场信息复制到编排后相对应的考场信息里面
        for (ScheduleResult scheduleResult : scheduleResults) {
            for (ScheduleExamRoom sExamRoom : scheduleResult.getExamRooms()) {
                ExamRoom examRoom = ScheduleHelper.getInstance().getExamRoomMap().get(sExamRoom.getRoomId());
                sExamRoom.setProvince(examRoom.getProvince());
                sExamRoom.setCity(examRoom.getCity());
                sExamRoom.setDistrict(examRoom.getDistrict());
                if (scheduleConstrain.getGroupAttrs().contains("levelFour")) {
                    sExamRoom.setDept(examRoom.getLevelFour());
                } else if (scheduleConstrain.getGroupAttrs().contains("levelThree")) {
                    sExamRoom.setDept(examRoom.getLevelThree());
                } else if (scheduleConstrain.getGroupAttrs().contains("levelTwo")) {
                    sExamRoom.setDept(examRoom.getLevelTwo());
                } else if (scheduleConstrain.getGroupAttrs().contains("level")) {
                    sExamRoom.setDept(examRoom.getLevel());
                }

            }
        }

        return scheduleResults;
    }

    /**
     * 虚拟考场编排入口
     */
    @Override
    public ImmutablePair<List<ScheduleResult>, List<ExamRoom>> scheduleVirRoom(ScheduleConstrain scheduleConstrain,
            List<ExamInfo> examInfos) {
        logger.info("start vir exam room schedule {} total batch number {}", scheduleConstrain, examInfos.size());
        ScheduleHelper scheduleHelper = ScheduleHelper.getInstance();
        Map<String, ScheduleResult> scheduleResultMap = new HashMap<>();

        try {

            // 根据考生数据、约束条件创建虚拟考场
            List<ExamRoom> examRooms = this.createVirExamRooms(scheduleConstrain, examInfos);

            logger.info("create vir exam room number {}", examRooms.size());

            // 将考场信息增加到编排缓存
            examRooms.forEach(item -> {
                scheduleHelper.getExamRoomMap().put(item.getRoomId(), item);
            });

            // 对考生和考场进行分组处理，同时生成考场考生对应的虚拟编排考生ID
            this.scheduleGroupHandler.scheduleGroup(scheduleConstrain, examRooms, examInfos);

            // 对考生和考场进行分组处理，同时生成考场考生对应的虚拟编排考生ID
            // 主要目的：1）考生和考场建立对应关系。2）将多个考生组合在一起转换成编排用的虚拟考生
            Map<String, Map<String, PackageExamRoomCandidate>> batchPackageGroupMap = scheduleHelper
                    .getBatchPackageGroupMap();

            // 取得批次号，并对批次号进行排序
            List<String> batchNoList = batchPackageGroupMap.keySet().stream().collect(Collectors.toList());
            Collections.sort(batchNoList);

            Map<String, List<PackageExamRoomCandidate>> batchGroupPackageMap = SupportUtils
                    .extractPackage(batchPackageGroupMap);

            for (Map.Entry<String, List<PackageExamRoomCandidate>> entry : batchGroupPackageMap.entrySet()) {
                String groupName = entry.getKey();
                Set<Candidate> candidateSet = new HashSet<>();
                Set<ExamRoom> examRoomSet = new HashSet<>();
                entry.getValue().forEach(item -> {
                    item.getCandidates().stream().forEach(citem -> {
                        candidateSet.addAll(citem);
                    });
                    examRoomSet.addAll(item.getExamRooms());
                });
                List<ScheduleResult> groupScheduleResult = this.schedule(scheduleConstrain, examInfos, groupName,
                        batchNoList,
                        examRoomSet, candidateSet);

                for (ScheduleResult scheduleResult : groupScheduleResult) {
                    if (!scheduleResultMap.containsKey(scheduleResult.getBatchNo())) {
                        scheduleResult.setPackageGroupMap(batchPackageGroupMap.get(scheduleResult.getBatchNo()));
                        scheduleResultMap.put(scheduleResult.getBatchNo(), scheduleResult);
                    } else {
                        scheduleResultMap.get(scheduleResult.getBatchNo()).getExamRooms()
                                .addAll(scheduleResult.getExamRooms());
                    }
                }
            }

            List<ScheduleResult> scheduleResults = scheduleResultMap.values().stream().collect(Collectors.toList());
            ScheduleStaticResult staticResult = ScheduleStatics.totalStaticSchedule(scheduleConstrain,
                    scheduleResults,
                    examRooms);
            logger.info("schedule over static result {}", staticResult);
            return ImmutablePair.of(scheduleResults, examRooms);
        } finally {
            this.clear();
        }

    }

    /**
     * 根据考场容量初始化考场座位编号
     * 
     * @param examRooms
     */
    private void initExamRoom(List<ExamRoom> examRooms) {
        examRooms.forEach(item -> {
            ScheduleHelper.getInstance().getCandidateExamRoomMap().clear();
            item.getIdlePosition().clear();
            item.getIdlePosition()
                    .addAll(IntStream.rangeClosed(1, item.getCapacity()).boxed().collect(Collectors.toList()));
        });
    }

    /***
     * 根据考生数、考试约束条件创建需要的虚拟考场
     * 
     * @param scheduleConstrain
     * @param examInfos
     * @return
     */
    private List<ExamRoom> createVirExamRooms(ScheduleConstrain scheduleConstrain,
            List<ExamInfo> examInfos) {
        Map<String, List<ExamRoom>> examRoomGroup = new HashMap<>();
        try {
            int virCapacity = scheduleConstrain.getVirCapacity();
            // 分组参数
            List<String> groupAttrs = new ArrayList<>(scheduleConstrain.getGroupAttrs());

            // 在分组信息里面增加对应的分组参数应答方式
            groupAttrs.add(0, "examType");

            this.scheduleGroupHandler.groupExamInfoCandidate(scheduleConstrain, examInfos,
                    groupAttrs);

            // batchNo, groupName:candidates
            Map<String, Map<String, List<Candidate>>> batchCandidateGroupMap = ScheduleHelper.getInstance()
                    .getCandidateGroupMap();

            for (Map.Entry<String, Map<String, List<Candidate>>> batchItem : batchCandidateGroupMap.entrySet()) {
                Map<String, List<Candidate>> groupCandidateMap = batchItem.getValue();

                // 计算该批次需要多少对应分组的考场容量, 并增加
                for (Map.Entry<String, List<Candidate>> groupItem : groupCandidateMap.entrySet()) {
                    String candidateGroupName = groupItem.getKey();
                    String examRoomGroupName = candidateGroupName;
                    if (!examRoomGroup.containsKey(examRoomGroupName)) {
                        examRoomGroup.put(examRoomGroupName, new ArrayList<>());
                    }
                    // 计算需要的考场，考虑编排方式的不同，采用较大的冗余，考场数 = ((考生数/考场容量) + 1) * 2
                    // 实际完成编排后，没有编排的虚拟考场不会返回给业务系统
                    int needExamRoomNumber = (int) (Math.floor((groupItem.getValue().size() / virCapacity)) + 1)
                            * 2;
                    if (needExamRoomNumber > 0) {
                        if (needExamRoomNumber > 0) {

                            List<ExamRoom> newExamRooms = this.createVirExamRoom(needExamRoomNumber,
                                    examRoomGroupName, virCapacity, groupAttrs, groupItem.getValue().get(0));
                            examRoomGroup.get(examRoomGroupName).addAll(newExamRooms);
                        }
                    }
                }

            }

            List<ExamRoom> examRooms = examRoomGroup.values().stream()
                    .flatMap(Collection::stream)
                    .collect(Collectors.toList());
            return examRooms;

        } finally {
            this.clear();
        }
    }

    private List<ExamRoom> createVirExamRoom(int number, String groupName, int capacity, List<String> groupAddrs,
            Candidate candidate) {
        List<ExamRoom> examRooms = new ArrayList<>(number);
        IntStream.range(0, number).forEach(index -> {
            ExamRoom examRoom = ExamRoom.builder().roomId(UUID.randomUUID().toString())
                    .virGroupName(groupName)
                    .capacity(capacity)
                    .build();
            examRoom.getIdlePosition()
                    .addAll(IntStream.rangeClosed(1, capacity).boxed().collect(Collectors.toList()));
            examRoom.setExamType(candidate.getExamType());
            examRoom.setLevelFour(candidate.getLevelFour());
            examRoom.setLevelThree(candidate.getLevelThree());
            examRoom.setLevelTwo(candidate.getLevelTwo());
            examRoom.setLevel(candidate.getLevel());
            examRoom.setProvince(candidate.getProvince());
            examRoom.setCity(candidate.getCity());
            examRoom.setCapacity(capacity);
            examRoom.setDistrict(candidate.getDistrict());
            examRooms.add(examRoom);
        });

        return examRooms;
    }

    public void clear() {
        ScheduleHelper.getInstance().clear();
        this.scheduleGroupHandler.clear();
    }

    @Data
    public static class PackageExamRoomCandidate {
        public String groupName;
        public List<ExamRoom> examRooms;
        public List<List<Candidate>> candidates;

        public PackageExamRoomCandidate(String groupName, List<ExamRoom> examRooms,
                List<List<Candidate>> candidates) {
            this.groupName = groupName;
            this.examRooms = examRooms;
            this.candidates = candidates;
        }

        public PackageExamRoomCandidate(String groupName) {
            this.groupName = groupName;
            this.examRooms = new ArrayList<>();
            this.candidates = new ArrayList<>();
        }

        public PackageExamRoomCandidate(String groupName, List<ExamRoom> examRooms) {
            this.groupName = groupName;
            this.examRooms = examRooms;
            this.candidates = new ArrayList<>();
        }
    }

}
